#pragma once

#include <iostream>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "lockguard.hpp"


namespace log_namespace
{
    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string levelToString(int level)
    {
        switch(level)
        {
            case DEBUG:
                return "DEBUG";
            case INFO:
                return "INFO";
            case WARNING:
                return "WARNING";
            case ERROR:
                return "ERROR";
            case FATAL:
                return "FATAL";
            default:
                return "UNKNOWN";
        }
    }

    std::string getCurtime()
    {
        time_t now = time(nullptr);
        struct tm *currtime = localtime(&now);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", \
            currtime->tm_hour,\
            currtime->tm_min,\
            currtime->tm_sec
            );
        return buffer;
    }

    class logmessage
    {
    public:
        std::string _level;
        pid_t  _id;
        std::string _filename;
        int _filenumber;
        std::string _curtime;
        std::string _msginfo;
    };

    #define SCREEN_TYPE 1
    #define FILE_TYPE 2

    const std::string glogfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    class Log
    {
    private:
        int _type;
        const std::string _logfile;
    public:
        Log(const std::string &logfile = glogfile)
            :_type(SCREEN_TYPE)
            ,_logfile(logfile)
        {

        }

        void enable(int type)
        {
            _type = type;
        }

        void flushLogToScreen(const logmessage& lg)
        {
            printf("[%s][%d][%s][%d][%s] %s", 
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curtime.c_str(),
                lg._msginfo.c_str()
            );
        }

        void flushLogToFile(const logmessage& lg)
        {
            // std::ios::app代表写文件是以 append模式进行的

            std::ofstream out(_logfile, std::ios::app);
            if (!out.is_open())
            {
                return;
            }

            char logtxt[2048];
            snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s", 
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curtime.c_str(),
                lg._msginfo.c_str()
            );

            out.write(logtxt, strlen(logtxt));
            out.close();
        }

        void flushLog(const logmessage& lg)
        {
            lockGuard lguard(&glock);

            switch(_type)
            {
                case SCREEN_TYPE:
                    flushLogToScreen(lg);
                    break;
                case FILE_TYPE:
                    flushLogToFile(lg);
                    break;
            }
        }

        void logMessage(std::string filename, int filenumber, int level, const char *format, ...)
        {
            logmessage lg;
            lg._level = levelToString(level);
            lg._id = getpid();
            lg._filename = filename;
            lg._filenumber = filenumber;
            lg._curtime = getCurtime();

            va_list ap;
            va_start(ap, format);
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap);
            va_end(ap);
            lg._msginfo = log_info;

            flushLog(lg);
        }

        ~Log()
        {

        }

    };

    Log lg;

    // __FILE__ 就是当前文件的文件路径，__LINE__就是当前文件的行号
    // 其中 do{} while(0) 单纯是为了保护下面定义的这个宏中的代码块的安全性
    // 其中##__VA_ARGS__就是为了防止传入空时，Format后面的那个逗号仍然存在
    #define LOG(Level, Format, ...) do{\
        lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__);\
    }while(0)

    #define EnableScreen() do{lg.enable(SCREEN_TYPE); } while(0)
    #define EnableFILE() do{lg.enable(FILE_TYPE); } while(0)

};